#include "GameAudio.h"

static const float DIST_FACTOR = 0.5f;

//-------------------------------------------------------------------------------------
GameAudio::GameAudio(void)
{
	mpListener = 0;
}
//-------------------------------------------------------------------------------------
GameAudio::~GameAudio(void)
{
	// Release all sounds in OpenAL
	for (SourceMap::iterator it = mSourceMap.begin(); it != mSourceMap.end(); ++it)
	{
		// Stop playing
		alSourceStop((*it).first);
	
		// Remove from OpenAL
		alDeleteSources(1, &((*it).first));
		alDeleteBuffers(1, &((*it).second));
	}
	mSourceMap.clear();
	for (AdvancedSourceMap::iterator it = mAdvancedSourceMap.begin(); it != mAdvancedSourceMap.end(); ++it)
	{
		// Stop playing
		alSourceStop((*it).first);
	
		// Remove from OpenAL
		alDeleteSources(1, &((*it).first));
		alDeleteBuffers(1, &((*it).second.buffer));
	}
	mSourceMap.clear();

	// Free cached sound files
	for (SoundMap::iterator it = mSoundMap.begin(); it != mSoundMap.end(); ++it)
	{
		// why?
		//free((*it).second.data);
	}

	alutExit();
}
//-------------------------------------------------------------------------------------
const bool GameAudio::init(void)
{
	return BaseAudio::init();
}
//-------------------------------------------------------------------------------------
const bool GameAudio::init(Ogre::Camera *cam)
{
	mpListener = cam;

	return BaseAudio::init();;
}
//-------------------------------------------------------------------------------------
void GameAudio::setListener(Ogre::Camera *cam)
{
	mpListener = cam;

	Ogre::Vector3 pos = mpListener->getPosition();
	mfListenerXPos = pos.x;
	mfListenerYPos = pos.y;
	mfListenerZPos = pos.z;
}
//-------------------------------------------------------------------------------------
void GameAudio::setListenerPosition(const ALfloat x, const ALfloat y, const ALfloat z)
{
	BaseAudio::setListenerPosition(x, y, z);
}
//-------------------------------------------------------------------------------------
void GameAudio::setPosition(const ALuint source, const ALfloat x, const ALfloat y, const ALfloat z)
{
	if (mpListener)
	{
		Ogre::Vector3 vec(x - mfListenerXPos, y - mfListenerYPos, z - mfListenerZPos);
		vec = mpListener->getOrientation() * vec;
		alSource3f(source, AL_POSITION, DIST_FACTOR * vec.x, DIST_FACTOR * vec.y, DIST_FACTOR * vec.z);
	}
	else
	{
		BaseAudio::setPosition(source, x, y, z);
	}
}
//-------------------------------------------------------------------------------------
void GameAudio::setGlobalVolume(const ALfloat volume)
{
	BaseAudio::setGlobalVolume(volume);

	// Update sound volume of sounds in OpenAL
	for (AdvancedSourceMap::iterator it = mAdvancedSourceMap.begin(); it != mAdvancedSourceMap.end(); ++it)
	{
		alSourcef((*it).first, AL_PITCH, mfVolume);
	}
}
//-------------------------------------------------------------------------------------
const ALfloat GameAudio::getGlobalVolume(void) const
{
	return BaseAudio::getGlobalVolume();
}
//-------------------------------------------------------------------------------------
const ALuint GameAudio::playSound(const SoundType type, const bool loop, const VisibleObjectPtr obj)
{
	// Check if this sound type is cached
	SoundMap::iterator it = mSoundMap.find(type);
	if (mSoundMap.find(type) == mSoundMap.end()) return 0;

	ALuint source;
	ALuint buffer;
	SoundResource res = (*it).second;

	// Generate a source and a buffer in OpenAL
	alGenSources(1, &source);
	alGenBuffers(1, &buffer);

	// Set parameters of the sound
	alSourcef(source, AL_PITCH, mfVolume);
	alSourcef(source, AL_GAIN, 1);
	Ogre::Vector3 pos = obj->getEntityNode()->_getDerivedPosition();
	setPosition(source, pos.x, pos.y, pos.z);
	alSource3f(source, AL_VELOCITY, 0, 0, 0);
	alSourcei(source, AL_LOOPING, loop);

	// Load sound in OpenAL
	alBufferData(buffer, res.format, res.data, res.size, ALsizei(res.freq));

	// Mark which buffer belongs to which source
	alSourcei(source, AL_BUFFER, buffer);

	// Check for errors
	if (alGetError() != AL_NO_ERROR) return 0;

	// Add sound to the map with advanced sounds in OpenAL
	SourceData sourceData = SourceData();
	sourceData.buffer = buffer;
	sourceData.obj = obj;
	mAdvancedSourceMap[source] = sourceData;

	playSound(source);

	return source;
}
//-------------------------------------------------------------------------------------
const ALuint GameAudio::playSound(const SoundType type, const bool loop)
{
	return BaseAudio::playSound(type, loop);
}
//-------------------------------------------------------------------------------------
void GameAudio::playSound(const ALuint source)
{
	BaseAudio::playSound(source);
}
//-------------------------------------------------------------------------------------
void GameAudio::stopSound(const ALuint source) const
{
	alSourceStop(source);
}
//-------------------------------------------------------------------------------------
void GameAudio::pauseSound(const ALuint source) const
{
	alSourcePause(source);
}
//-------------------------------------------------------------------------------------
void GameAudio::pauseAllSounds(void)
{
	// Pause all basic sounds
	for (SourceMap::iterator it = mSourceMap.begin(); it != mSourceMap.end(); ++it)
	{
		if ((*it).first == mBGMusic) continue;
		if (isPlaying((*it).first))
		{
			mPausedList.push_back((*it).first);
			alSourcePause((*it).first);
		}
	}

	// Pause all advanced sounds
	AdvancedSourceMap::iterator it_end = mAdvancedSourceMap.end();
	AdvancedSourceMap::iterator it = mAdvancedSourceMap.begin();
	for (; it != it_end; ++it)
	{
		if (isPlaying((*it).first))
		{
			mPausedList.push_back((*it).first);
			alSourcePause((*it).first);
		}
	}
}
//-------------------------------------------------------------------------------------
void GameAudio::resumeAllSounds(void)
{
	// Resume all paused sounds
	for (std::list<ALuint>::iterator it = mPausedList.begin(); it != mPausedList.end(); ++it)
	{
		BaseAudio::playSound(*it);
	}
	mPausedList.clear();
}
//-------------------------------------------------------------------------------------
void GameAudio::releaseSound(const ALuint source)
{
	BaseAudio::releaseSound(source);

	// Check if the sound was already released
	AdvancedSourceMap::iterator it = mAdvancedSourceMap.find(source);
	if (it == mAdvancedSourceMap.end()) return;

	// Stop playing
	alSourceStop(source);

	// Remove from OpenAL
	alDeleteSources(1, &source);
	alDeleteBuffers(1, &((*it).second.buffer));

	// Remove from the map with all sounds in OpenAL
	mAdvancedSourceMap.erase(it);
}
//-------------------------------------------------------------------------------------
const bool GameAudio::isPlaying(const ALuint source)
{
	return BaseAudio::isPlaying(source);
}
//-------------------------------------------------------------------------------------
void GameAudio::update(void)
{
	if (!mPausedList.empty()) return;

	ALint loop;
	Ogre::Vector3 pos;
	std::pair<ALuint, SourceData> data;

	// Update position of the listener
	if (mpListener)
	{
		pos = mpListener->getPosition();
		mfListenerXPos = pos.x;
		mfListenerYPos = pos.y;
		mfListenerZPos = pos.z;
	}

	// Update positions of all advanced sounds
	AdvancedSourceMap::iterator it_end = mAdvancedSourceMap.end();
	AdvancedSourceMap::iterator it = mAdvancedSourceMap.begin();
	for (; it != it_end;)
	{
		data = (*it);
		++it;

		if (data.second.obj->dead())
		{
			releaseSound(data.first);
			continue;
		}

		pos = data.second.obj->getEntityNode()->_getDerivedPosition();
		setPosition(data.first, pos.x, pos.y, pos.z);

		alGetSourcei(data.first, AL_LOOPING, &loop);
		if (!loop && !isPlaying(data.first))
		{
			releaseSound(data.first);
		}
	}
}
//-------------------------------------------------------------------------------------
void GameAudio::setBGMusic(const SoundType type)
{
	mBGMusic = playSound(type, true);
}
//-------------------------------------------------------------------------------------
void GameAudio::stopBGMusic(void)
{
	if (mBGMusic) stopSound(mBGMusic);
}